
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
//#include <gc_allocator.h>
#include <iostream>

#include "Boolean.h"
#include "ErrorObject.h"
#include "Function.h"
#include "List.h"
#include "Null.h"
#include "Object.h"
#include "String.h"
#include "Undefined.h"
#include "Value.h"
#include "ValueType.h"


Object* Object::prototype = new Object(Null::null, NULL, new String("Object"));
Object* Object::global = new Object(Null::null, NULL, new String("Object"));

Object::Object()
    : m_value(Null::null), m_prototype(Object::prototype),
    m_class(new String("Object"))
{
	m_lock = NULL;
}

Object::Object(Value* value)
    : m_value(value), m_prototype(Object::prototype),
    m_class(new String("Object"))
{
	m_lock = NULL;
}

Object::Object(Value* value, Object* prototype, String* clss)
    : m_value(value), m_prototype(prototype), m_class(clss)
{
	m_lock = NULL;
}

ValueType Object::getType() const
{
    return VT_OBJECT;
}

Boolean* Object::toBoolean()
{
    return Boolean::True;
}

Object*Object::toObject()
{
    return this;
}

Value* Object::toPrimitive()
{
    return this->defaultValue();
}

Value* Object::toPrimitive(ValueType type)
{
    return this->defaultValue(type);
}

Number* Object::toNumber()
{
    // 1. Call toPrimitive(hint Number).
    // 2. Call toNumber
    // 3. Return the result
    Value* prim = this->toPrimitive(VT_NUMBER);
    return prim->toNumber();
}

String* Object::toString()
{
    // 1. Call toPrimitive, hint String
    // 2. Call toString(result(1))
    // 3. Return result(2)
    return toPrimitive(VT_STRING)->toString();
}

bool Object::has(String* name, PropertyMap::iterator* piter)
{
    PropertyMap::iterator iter = m_properties.find(name);
    if (piter)
    {
        *piter = iter;
    }
    return iter != m_properties.end();
}

Value* Object::get(String* name)
{
    // Ecma 8.6.2.1
    // 1. if we don't have a property with the given name, go to step 4.
    PropertyMap::iterator iter;
    if (!has(name, &iter))
    {
        // 4. If the [[Prototype]] of O is null, return undefined
        if (m_prototype == NULL)
        {
            return Undefined::undefined;
        }

        // 5. Call the [[Get]] method of [[Prototype]] with name
        // 6. Return that result
        return m_prototype->get(name);
    }

    // 2. Get the value of the property
    // 3. Return that result.
    return iter->second.first;
}

Boolean* Object::hasProperty(String* name)
{
    // Ecma 8.6.2.4
    // 1. If O has a property with name, return true
    if (this->has(name))
    {
        return Boolean::True;
    }

    // 2. If the [[prototype]] of o is null, return false
    if (m_prototype == NULL)
    {
        return Boolean::False;
    }

    // 3. Call the has property method of prototype with name
    // 4. Return that result
    return m_prototype->hasProperty(name);
}

void Object::put(String* name, Value* value, unsigned char attrib)
{
    // Ecma 8.6.2.2
    // 1. Call the [[canPut]] method of O with name p
    // 2. If result is false, return
    PropertyMap::iterator iter;
    if (!canPut(name, &iter))
    {
        return;
    }

    // 3. If O doesn't have a property with name p, go to step 6
    if (iter != m_properties.end())
    {
        // 4. Set the value of property to V. The attributes are unchanged
        // just modify the existing property
        // TODO: is | the right way to set the attributes?
        iter->second.first = value;
        iter->second.second = iter->second.second | attrib;

        // 5. Return
        return;
    }

    // 6. 
    // Create a property with name P, set its value to V, and give
    // it empty attributes
    m_properties.insert(std::pair<String*, std::pair<Value*, unsigned char> >(name, std::pair<Value*, unsigned char>(value, attrib)));

    // 7. Return

}

bool Object::canPut(String* name, PropertyMap::iterator* piter)
{
    // Ecma 8.6.2.3 [[canPut]]
    // 1. If o doesn't have a property with name P, go to step 4
    PropertyMap::iterator iter = m_properties.find(name);
    if (piter)
    {
        *piter = iter;
    }

    if (iter != m_properties.end())
    {
        // 2. if the property has the read only attribute, return true
        if (iter->second.second & PA_READ_ONLY)
        {
            return false;
        }

        // 3. return true
        return true;
    }

    // 4. if [[prototype]] is null, return true
    if (m_prototype == NULL)
    {
        return true;
    }

    // 5. Call can put on prototype
    // 6. return the result
    return m_prototype->canPut(name);
}

Boolean* Object::deleteProperty(String* name)
{
    // Ecma 8.6.2.5 [[Delete]]
    // 1. If O doesn't have a property with name, return true
    PropertyMap::iterator iter;
    if (!has(name, &iter))
    {
        return Boolean::True;
    }

    // 2. If the property has the DontDelete attribute, return false.
    if (iter->second.second & PA_DONT_DELETE)
    {
        return Boolean::False;
    }

    // 3. Remove the property with name from O
    m_properties.erase(iter);

    // 4. Return true
    return Boolean::True;
}

Value* Object::defaultValue()
{
    // When the [[Defaultvalue]] is called with no hint, then it
    // behaves as if the hint were Number unless we are a Date object,
    // in which case we behave as if hint were String.
    return String::equals(m_class, new String("Date"))->isTrue() ? 
        defaultValue(VT_STRING) : defaultValue(VT_NUMBER);
}

Value* Object::defaultValue(ValueType type)
{
    // Ecma 8.6.2.6
    switch (type)
    {
        case VT_STRING:
        {
            // 1. Call the [[get]] method of object O with argument "toString"
            Value* tostr = get(new String("toString"));

            // 2. If result is not an object, go to step 5.
            if (tostr->getType() == VT_OBJECT)
            {
                // 3. Call the [[Call]] method of tostr, with O as the
                // this value, and an empty argument list
                Value* prim = tostr->function()->call(NULL, this, new List());

                // 4. If the result is a primitve value, return it
                if (isPrimitive(prim->getType()))
                {
                    return prim;
                }
            }

            // 5. Call the [[get]] method of object O with argument valueOf
            Value* valof = get(new String("valueOf"));

            // 6. If result is not an object, go to step 9
            if (valof->getType() == VT_OBJECT)
            {
                // 7. Call the call method of valof with O as this and
                // empty argument list
                Value* prim = valof->function()->call(NULL, this, new List());

                // 8. If result is a primitive value, return it
                if (isPrimitive(prim->getType()))
                {
                    return prim;
                }
            }

            // 9. throw a TypeError exception
            return esiply_throw(ET_TYPE,
                    "Unable to convert object to a primitive (hint: string)");
        }

        case VT_NUMBER:
        {
            // 1. Call the [[get]] method of O with argument "valueOf"
            Value* valof = get(new String("valueOf"));
            
            // 2. If result is not an object, go to step 5
            if (valof->getType() == VT_OBJECT)
            {
                // 3. Call the call method of valof with O as this
                // and empty argument
                Value* prim = valof->function()->call(NULL, this, new List());

                // 4. If result is a primitive value, return it
                if (isPrimitive(prim->getType()))
                {
                    return prim;
                }

            }

            // 5. Call the [[get]] method of object O with "toString"
            Value* tostr = get(new String("toString"));

            // 6. If not an object, go to step 9
            if (tostr->getType() == VT_OBJECT)
            {
                // 7. call that with O as this and empty args
                Value* prim = tostr->function()->call(NULL, this, (new List()));

                // 8. If primitive, return it
                if (isPrimitive(prim->getType()))
                {
                    return prim;
                }
            }

            // 9. Throw a TypeError exception
            return esiply_throw(ET_TYPE,
                    "Unable to convert object to a primitive (hint: number)");
        }

        default:
            assert(false);
    }

    // We should never get here.
    assert(false);
    return Undefined::undefined;
}

bool Object::isCallable() const
{
    return false;
}


void Object::get_enumerable(std::set<String*, ltstring>& enumerable)
{
    // Add any enumerable properties of this object to the set
    PropertyMap::iterator iter;
    for (iter = m_properties.begin(); iter != m_properties.end(); iter++)
    {
        if (!(iter->second.second & PA_DONT_ENUM))
        {
            enumerable.insert(iter->first);
        }
    }

    // Now add all the prototype's properties
    if (m_prototype)
    {
        m_prototype->get_enumerable(enumerable);
    }
}

Object* Object::get_prototype() const
{
    return m_prototype;
}

void Object::set_prototype(Object* proto)
{
    assert(proto != this);
    m_prototype = proto;
}

Value* Object::get_value() const
{
    return m_value;
}

void Object::set_value(Value* value)
{
    m_value = value;
}

String* Object::get_class() const
{
    return m_class;
}

void Object::set_class(String* clss)
{
    m_class = clss;
}

unsigned char Object::get_attributes(String* name)
{
    PropertyMap::iterator iter = m_properties.find(name);
    if (iter != m_properties.end())
    {
        return iter->second.second;
    }

    // TODO: not sure what I want to do here.
    return 0;
}

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void Object::lock(){
	pthread_mutex_lock(&mutex);
	if(this->m_lock == NULL){
		this->m_lock = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));
		pthread_mutex_init(this->m_lock,NULL);
		
	}
	//std::cerr<<"lock\n";
	//lock the Object
	pthread_mutex_lock(this->m_lock);
	pthread_mutex_unlock(&mutex);
}
void Object::unlock(){
	if(this->m_lock != NULL)
		pthread_mutex_unlock(this->m_lock);
	//std::cerr<<"unlock\n";
}
Object::~Object(){
	if(this->m_lock != NULL){
		//destroy mutex object
		pthread_mutex_destroy(this->m_lock);
		free(this->m_lock);
		this->m_lock = NULL;
	}
}